/*
 * Copyright 2015-2016 Tumblr, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tumblr.backboard.imitator;

import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringListener;
import com.tumblr.backboard.MotionProperty;
import ohos.multimodalinput.event.TouchEvent;

/**
 * that moves freely when the user is not dragging it. It copies to use when the user is dragging.
 *
 * @author ericleong
 * @since 2021-05-07
 */
public class InertialImitator extends ConstrainedMotionImitator implements SpringListener {
    /**
     * The friction (in {@link com.facebook.rebound.SpringConfig}) to use when moving freely.
     */
    public static final float DEFAULT_FRICTION = 1.0f;

    /**
     * The {@link com.facebook.rebound.SpringConfig} to use when moving freely.
     */
    public static final SpringConfig SPRING_CONFIG_FRICTION = new SpringConfig(0, DEFAULT_FRICTION);

    /**
     * Used to convert {@link com.facebook.rebound.Spring#getVelocity()} to the units needed by {@link
     * #calculateRestPosition()}.
     */
    public static final int VELOCITY_RATIO = 24;

    /**
     * The {@link SpringConfig} to use when being dragged.
     */
    protected SpringConfig mOriginalConfig;

    /**
     * Constructor. Uses {@link #TRACK_ABSOLUTE} and {@link #FOLLOW_EXACT}.
     *
     * @param property the desired property to imitate
     * @param minValue the minimum value
     * @param maxValue the maximum value
     */
    public InertialImitator(MotionProperty property, double minValue, double maxValue) {
        super(property, minValue, maxValue);
    }

    /**
     * Constructor.
     *
     * @param property the desired property to imitate
     * @param trackStrategy the tracking strategy.
     * @param followStrategy the follow strategy.
     * @param minValue the desired minimum spring value.
     * @param maxValue the desired maximum spring value.
     */
    public InertialImitator(MotionProperty property, int trackStrategy, int followStrategy, double minValue,
            double maxValue) {
        super(property, trackStrategy, followStrategy, minValue, maxValue);
    }

    /**
     * Sets the {@link com.facebook.rebound.Spring} that this imitator should use. This class attaches itself as a
     * {@link com.facebook.rebound.SpringListener} and stores the {@link com.facebook.rebound.SpringConfig} to use when
     * the user is dragging.
     *
     * @param spring the spring to use
     */
    @Override
    public void setSpring(Spring spring) {
        super.setSpring(spring);
        spring.addListener(this);

        mOriginalConfig = spring.getSpringConfig();
    }

    @Override
    public void constrain(TouchEvent event) {
        super.constrain(event);

        mSpring.setSpringConfig(mOriginalConfig);
    }

    @Override
    public void release(TouchEvent event) {
        // snap to left or right depending on current location
        double restPosition = calculateRestPosition();
        if (mSpring.getCurrentValue() > mMaxValue && restPosition > mMaxValue) {
            mSpring.setEndValue(mMaxValue);
        } else if (mSpring.getCurrentValue() < mMinValue && restPosition < mMinValue) {
            mSpring.setEndValue(mMinValue);
        } else {
            mSpring.setSpringConfig(SPRING_CONFIG_FRICTION);
            mSpring.setEndValue(Double.MAX_VALUE);
        }
    }

    /**
     * calculate rest position
     *
     * @return the spring position when it comes to rest (given infinite time).
     */
    private double calculateRestPosition() {
        return mSpring.getCurrentValue() + VELOCITY_RATIO * mSpring.getVelocity()
                / (mSpring.getSpringConfig().friction);
    }

    /**
     * setMinValue
     *
     * @param minValue minValue
     */
    public void setMinValue(double minValue) {
        this.mMinValue = minValue;
    }

    /**
     * setMaxValue
     *
     * @param maxValue maxValue
     */
    public void setMaxValue(double maxValue) {
        this.mMaxValue = maxValue;
    }

    @Override
    public void onSpringUpdate(Spring spring) {
        if (mSpring != null) {
            double restPosition = calculateRestPosition();
            if (mSpring.getSpringConfig().equals(SPRING_CONFIG_FRICTION)) {
                if (mSpring.getCurrentValue() > mMaxValue && restPosition > mMaxValue) {
                    mSpring.setSpringConfig(mOriginalConfig);
                    mSpring.setEndValue(mMaxValue);
                } else if (mSpring.getCurrentValue() < mMinValue && restPosition < mMinValue) {
                    mSpring.setSpringConfig(mOriginalConfig);
                    mSpring.setEndValue(mMinValue);
                }
            }
        }
    }

    @Override
    public void onSpringAtRest(Spring spring) {
        // pass
    }

    @Override
    public void onSpringActivate(Spring spring) {
        // pass
    }

    @Override
    public void onSpringEndStateChange(Spring spring) {
        // pass
    }
}
